//----------------------------------------------------------------------------------------------
//	Filename:	PointCloud.h
//	Author:		Keith Bugeja
//	Date:		31/07/2012
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
#pragma once

#include <boost/shared_ptr.hpp>

#include "Shape/Shape.h"
#include "Geometry/BoundingBox.h"
#include "Threading/List.h"
#include "Maths/Random.h"
#include "Shape/CloudPoint.h"
#include "Scene/Environment.h"

namespace Illumina 
{
	namespace Core
	{		
		class IPointCloud
			: public IShape
		{
		protected:
			// PointCloud area
			float m_fArea;
			
			// Model axis-aligned bounding box
			AxisAlignedBoundingBox m_boundingBox;

			// Uniform random number generator for sampling
			Random m_random;

			// Radius of sphere representing each point
			float m_fSphereRadius;
			float m_fSphereRadiusSq;

			static int MaterialGroupIds;
			static int NumberOfMaterials;

			int m_world_type; //PLWF = 1, LWSF = 0;

			float m_surface_curvature;
			float m_corner_ratio_epsilon;
			float m_noise;
			float m_ballRadius;
			int m_pcsId;

			int m_kNNType, m_kNNFlood;
			float m_kNNTypeRadius, m_kNNFloodRadius;
		    int m_kNNTypeMode, m_kNNFloodMode;

			float m_min_dist, m_max_dist, m_radius;

			float m_coplanarDst;

			int m_edgeCornerDetect;

			std::vector<int> m_pointTypesToDiscard;

			std::string m_trunk;
			bool m_toScan;
			int m_scans;

			Environment * m_environment;

		public:
			std::vector<std::pair<int, int>> m_pointListParts;  // std::pair<offset, count>
			std::vector<CloudPoint> m_pointList;
			std::map<int, CloudPoint *> m_edgePointsMap;
			std::map<int, CloudPoint *> m_allPointsMap;
			std::vector<int> m_pointListSegmentIdsCache;

		public:
			IPointCloud(const std::string &p_strName);
			IPointCloud(void);

			//Method for setting radius
			void SetRadius(float p_radius)
			{
				m_fSphereRadius = p_radius;
				m_fSphereRadiusSq = m_fSphereRadius * m_fSphereRadius;				
				std::cout << "[PointCloud - Setting Sphere Radius] Setting radius to " << m_fSphereRadius << std::endl;
			}

			void SetEnvironment(Environment * p_env) { m_environment = p_env; }

			float GetRadius() { return m_fSphereRadius; }
			
			void SetToScan(bool p_scan) {m_toScan = p_scan; }
			bool GetToScan() { return m_toScan; }

			void SetPCSId(int p_pcsId) { m_pcsId = p_pcsId; }
			int GetPCSId() { return m_pcsId; }

			void SetNumberOfScans(int p_scans) { m_scans = p_scans; }
			int GetNumberOfScans() { return m_scans; }

			void AddPointCloudPart(int p_count) 
			{ 
				//int offset = 0;
				//if (m_pointList.size() > 0) offset = m_pointList.size();				
				m_pointListParts.push_back(std::pair<int, int>(m_pointList.size(),p_count)); 
			}

			static void ResetMaterialsIds();
			static int GetNextMaterialId();
			static int GetNumberOfMaterials();

			void SetEdgeCornerDetect(int p_edgeCornerDetect) { m_edgeCornerDetect = p_edgeCornerDetect; }
			int GetEdgeCornerDetect() { return m_edgeCornerDetect; }

			//SetTrunk (path to data file)
			void SetTrunk(std::string p_trunk) { m_trunk = p_trunk; }
			std::string GetTrunk() { return m_trunk; }

			// SetType 
			void SetWorldType(int p_type) { m_world_type = p_type; }
			int GetWorldType() {return m_world_type; }

			void AddPointTypesToPrune(int p_toAdd) { m_pointTypesToDiscard.push_back(p_toAdd); }
			std::vector<int> GetPointTypesToPrune() { return m_pointTypesToDiscard; }

			// nearest neighbour computation parameter methods
			void SetkNNType(int p_k) { m_kNNType = p_k; }
			void SetkNNFlood(int p_k) { m_kNNFlood = p_k; }
			void SetkNNTypeMode(int p_k) { m_kNNTypeMode = p_k; }
			void SetkNNFloodMode(int p_k) { m_kNNFloodMode = p_k; }
			void SetkNNTypeRadius(float m_tr) { m_kNNTypeRadius = m_tr; }
			void SetkNNFloodRadius(float m_tr) { m_kNNFloodRadius = m_tr; }

			int GetkNNType() { return m_kNNType; }
			int GetkNNFlood() { return m_kNNFlood; }									
			int GetkNNTypeMode() { return m_kNNTypeMode; }
			int GetkNNFloodMode() { return m_kNNFloodMode; }
			float GetkNNTypeRadius() { return m_kNNTypeRadius; }
			float GetkNNFloodRadius() { return m_kNNFloodRadius; }

			void SetMinDist(float p_min_dist) { m_min_dist = p_min_dist; }
			void SetMaxDist(float p_max_dist) { m_max_dist = p_max_dist; }
			void SetRadius2(float p_radius)	  { m_radius = p_radius; }

			float GetMinDist() { return m_min_dist; }
			float GetMaxDist() { return m_max_dist; }
			float GetRadius2() { return m_radius; }

			void SetCoplanarDst(float p_coplanarDst) { m_coplanarDst = p_coplanarDst; }
			float GetCoplanarDst() { return m_coplanarDst; }

			//Surface Curvature and Corner Ratio methods
			void SetSurfaceCurvature(float p_curvature) { m_surface_curvature = p_curvature; }
			float GetSurfaceCurvature() { return m_surface_curvature; }

			void SetCornerRatioEpsilon(float p_corner_ratio_epsilon) { m_corner_ratio_epsilon = p_corner_ratio_epsilon; }
			float GetCornerRatioEpsilon() { return m_corner_ratio_epsilon; }

			//Noise filters
			void SetNoise(float p_noise) { m_noise = p_noise; }
			float GetNoise() { return m_noise; }

			//Corner Detection Ball Radius
			void SetBallRadius(float p_ballRadius) { m_ballRadius = p_ballRadius; }
			float GetBallRadius() { return m_ballRadius; }

			// Methods for instance creation
			virtual boost::shared_ptr<IPointCloud> CreateInstance(void) = 0;

			// Bounding volume
			bool IsBounded(void) const;
			void ComputeBoundingVolume(void);
			IBoundingVolume* GetBoundingVolume(void) const;

			int GetSize(void);

			// Area methods
			void ComputeArea(void);
			float GetArea(void) const;

			// Normals
			bool UpdateNormals(void);

			// Vertex management			
			size_t AddPoint(const CloudPoint &p_vertex);
			void AddPointList(const CloudPoint *p_pVertex, int p_nCount);
			void AddPointList(const List<CloudPoint> &p_vertexList);

			// Compile method used to prepare complex structures for usage (e.g., BVHs)
			bool IsCompilationRequired(void) const { return true; }
			bool Compile(void) { return true; }

			// Update and rebuild methods for dynamic meshes
			bool Update(void) { return true; }
			bool Rebuild(void) { return true; }

			// Sampling methods
			float GetPdf(const Vector3 &p_point) const;
			Vector3 SamplePoint(float p_u, float p_v, Vector3 &p_normal);
			Vector3 SamplePoint(const Vector3 &p_viewPoint, float p_u, float p_v, Vector3 &p_normal);

			// Intersect methods
			virtual bool Intersects(const Ray &p_ray, DifferentialSurface &p_surface) = 0;
			virtual bool Intersects(const Ray &p_ray) = 0;
		};		
	} 
}